package com.terren.spider.core.html.util;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.collections.CollectionUtils;

import com.jayway.jsonpath.PathNotFoundException;
import com.terren.spider.core.html.configuration.Configuration;
import com.terren.spider.entity.config.SysConfig;
import com.terren.spider.entity.core.Entry;
import com.terren.spider.entity.core.FieldType;
import com.terren.spider.entity.core.Regular;
import com.terren.spider.entity.core.RegularGroupDiv;
import com.terren.spider.entity.core.SelectorType;
import com.terren.spider.util.BasicUtil;
import com.terren.spider.util.common.BeanUtil;
import com.terren.spider.util.common.DateUtil;
import com.terren.spider.util.common.StringUtil;

import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.selector.Html;

public class HtmlParseUtil<T> extends BasicUtil {
	private List<T> collectItems;// 收集的items
	private Integer parseCount;// 分析过的条目数
	protected Configuration configuration;

	public HtmlParseUtil(Configuration configuration) throws Exception {
		super();
		this.configuration = configuration;
		collectItems = new ArrayList<T>();// 收集的items
		parseCount = 0;
	}

	public T parse(String html, Entry entry, Class<T> clazz) throws Exception {
		return parse(null, html, entry, clazz);
	}

	public T parse(Page page, Entry entry, Class<T> clazz) throws Exception {
		return parse(page, page.getRawText(), entry, clazz);
	}

	/**
	 * 解析HTML 并返回实体
	 * 
	 * @param page
	 * @param clazz
	 * @return
	 * @throws Exception
	 */
	public T parse(Page page, String html, Entry entry, Class<T> clazz) throws Exception {
		// 得到pojo所定义的字段
		T pojo = clazz.newInstance();
		Html parseHtml = Html.create(html);//
		List<Field> fields = BeanUtil.getAllFields(clazz);
		for (Field field : fields) {
			// 设置字段可访问（必须，否则报错）
			field.setAccessible(true);
			// 得到字段的属性名
			String fieldName = field.getName();
			Class<?> fieldType = field.getType();

			if (null == fieldType) {
				continue;
			}

			if (fieldName.equals("html")) {
				if (page != null) {
					Method method = pojo.getClass().getMethod("set" + StringUtil.toFirstUpperCase(fieldName),
							fieldType);
					method.invoke(pojo, fieldType.cast(StringUtil.castString(page.getRawText(), fieldType)));
					continue;
				}
			}

			if (fieldName.equals("url")) {
				if (page != null) {
					String realUrl = page.getRequest().getExtra(SysConfig.LAST_REDIRECT_URL) != null
							? (String) page.getRequest().getExtra(SysConfig.LAST_REDIRECT_URL) : null;
					String targetUrl = page.getRequest().getExtra("url") != null
							? (String) page.getRequest().getExtra("url") : null;
					String url = realUrl != null ? realUrl : targetUrl != null ? targetUrl : page.getUrl().toString();
					Method method = pojo.getClass().getMethod("set" + StringUtil.toFirstUpperCase(fieldName),
							fieldType);
					method.invoke(pojo, fieldType.cast(StringUtil.castString(url, fieldType)));
					continue;
				}
			}

			List<Regular> regulars = new ArrayList<>();// 支持多个Regular，因为很多网站经历过改版，所以相同内容可能对应的div
														// select可能不一样
			if (CollectionUtils.isNotEmpty(entry.getRegulars())) {
				for (Regular config : entry.getRegulars()) {
					if (config.getRegulartype() != null && config.getRegulartype().equalsIgnoreCase(fieldName)) {
						regulars.add(config);
					}
				}
			}

			if (regulars.size() > 0) {
				for (Regular r : regulars) {// 页面某个属性的相关配置
					try {
						if (null == r.getSelect()) {
							continue;
						}
						if (fieldType.isEnum()) {
							fieldType = String.class;
						}
						String selector = "";
						switch (SelectorType.valueOfType(r.getSelectorType())) {// 抓取页面
																				// 选择器选择
																				// CSS
																				// (0)
																				// |
																				// XPATH
																				// (1)
						case CSS:
							if (null == r.getAttr() || r.getAttr().equals("") || r.getAttr().equals("-")) {// 如果属性attr没有值，设置为text，用来选取标签内的文本内容
								r.setAttr("text");
							}
							selector = parseHtml.css(r.getSelect(), r.getAttr()).toString();
							break;
						case XPATH:
							selector = parseHtml.xpath(r.getSelect()).toString();
							break;
						case JSON_PATH:
							// page.getJson().removePadding("callback").jsonPath("$."+r.getSelect()).get();
							try {
								if (page.getJson().jsonPath(r.getSelect()) != null) {
									if (page.getJson().jsonPath(r.getSelect()).all().size() > 1) {
										selector = page.getJson().jsonPath(r.getSelect()).all().toString();
									} else {
										selector = page.getJson().jsonPath(r.getSelect()).get();
									}
								}
							} catch (PathNotFoundException e) {

								log.info("[" + r.getSelect() + "] No results for path");
							}

							break;
						default:
							if (null == r.getAttr() || r.getAttr().equals("") || r.getAttr().equals("-")) {// 如果属性attr没有值，设置为text，用来选取标签内的文本内容
								r.setAttr("text");
							}
							selector = parseHtml.css(r.getSelect(), r.getAttr()).toString();
							break;
						}

						if (selector != null && !selector.equals("")) {
							String formatter = selector;

							if (r.getFormat() != null && !r.getFormat().equals("")&& !r.getFormat().equals("-")) {//
								formatter = format(selector, r, entry);
							}
							Method method = pojo.getClass().getMethod("set" + StringUtil.toFirstUpperCase(fieldName),
									fieldType);
							method.invoke(pojo, fieldType.cast(StringUtil.castString(formatter, fieldType)));
							break;// 解析到内容则退出循环
						}
					} catch (Exception e) {
						log.error("set field error [url:" + page.getUrl().toString() + "]");
						log.error("set field error [" + r.getSelect() + "]", e);
					}
				}
			} // end if
		}
		collectItems.add(pojo);
		parseCount++;
		return pojo;
	}

	/**
	 * 处理回帖
	 * 
	 * @param page
	 * @param childHtml
	 * @param clazz
	 * @return
	 * @throws Exception
	 */
	public List<T> parseList(Page page, Entry entry, Class<T> clazz) throws Exception {
		List<T> tList = new ArrayList<T>();

		List<String> partList = new ArrayList<String>();
		if (entry.getDivList() != null && entry.getDivList().size() > 0) {
			for (RegularGroupDiv rgd : entry.getDivList()) {
				switch (SelectorType.valueOfType(rgd.getSelectorType())) {
				case CSS:
					partList.addAll(page.getHtml().css(rgd.getSelect()).all());
					break;
				case XPATH:
					partList.addAll(page.getHtml().xpath(rgd.getSelect()).all());
					break;
				default:
					break;
				}
			}
		} else {
			partList.add(page.getRawText());
		}
		if (partList != null && partList.size() != 0) {
			for (String pageStr : partList) {
				// 得到pojo所定义的字段
				T pojo = parse(page, pageStr, entry, clazz);

				tList.add(pojo);
			}
		}

		collectItems.addAll(tList);
		parseCount++;
		return tList;
	}

	/**
	 * 格式转换
	 * 
	 * @param string
	 * @param formatter
	 * @param fieldName
	 * @param fieldType
	 * @return
	 */
	public String format(String string, Regular r, Entry entry) {
		String formatter = r.getFormat();
		String fieldName = r.getRegulartype();
		String regex = r.getRegex();
		FieldType fieldType = FieldType.valueOfType(r.getFieldtype());
		// 如果有正则，则通过正则过滤掉string中的无效内容
		if (!"".equals(regex) && regex != null) {
			string = StringUtil.getReplaceStr(string, regex);
		}

		if (formatter != null && !"".equals(formatter)&& !"-".equals(formatter)) {

			switch (fieldType) {
			case STRING:
				return StringUtil.formatString(string, formatter, fieldName);
			case DOUBLE:
				return String.valueOf(StringUtil.formatDouble(string, formatter, fieldName));
			case DATE:
				String timeStr = string;
				String formatterTemp = StringUtil.formatTimeFor(formatter);
				if (!formatterTemp.equals(formatter)) {
					timeStr = StringUtil.formatString(string, formatter, formatterTemp);
				}
				return DateUtil.formatDateString(timeStr, formatterTemp, entry.getLocale());
			default:
				return StringUtil.formatString(string, formatter, fieldName);
			}
		}
		return string;
	}

	public Integer getParseCount() {
		return parseCount;
	}

	public void setParseCount(Integer parseCount) {
		this.parseCount = parseCount;
	}

	public List<T> getCollectItems() {
		return collectItems;
	}

	public void setCollectItems(List<T> collectItems) {
		this.collectItems = collectItems;
	}

	public Configuration getConfiguration() {
		return configuration;
	}

	public void setConfiguration(Configuration configuration) {
		this.configuration = configuration;
	}

}
